using System;
using System.Collections.Generic;
using Heaven.Mathematics;
using Heaven.Graphics;
using Heaven.Geometry;
using Heaven.Engine.Animation;
using Heaven.Engine.Transformations;

namespace Heaven.Engine
{
    /// <summary>
    /// Represents visual entity
    /// </summary>
    public class Visual : Node
    {
        #region Events

        // Weak events
        WeakMulticastDelegate absoluteInvalidated = new WeakMulticastDelegate();
        WeakMulticastDelegate polygonalInvalidated = new WeakMulticastDelegate();
  
        /// <summary>
        /// Occurs when node's absolute transformation has been invalidated
        /// </summary>
        internal event EventHandler AbsoluteInvalidated
        {
            add { absoluteInvalidated.Add(value); }
            remove { absoluteInvalidated.Remove(value); }
        }

        /// <summary>
        /// Occurs when node's polygonal object has been changed
        /// </summary>
        internal event EventHandler PolygonalInvalidated
        {
            add { polygonalInvalidated.Add(value); }
            remove { polygonalInvalidated.Remove(value); }
        }
                

        #endregion

        #region Fields

        // Hierarchy. It's used to implement 
        // hierarchical transformations
        Visual hierarchy = null;
        // Local transformation relative to the parent node
        ComplexTransformation transformation = new ComplexTransformation();

        // Rendering technique of the node
        Technique technique = null;
        // Properties of the node
        PropertyCollection properties = new PropertyCollection();

        // Geometry to render this node
        Polygonal polygonal = null;
                
        // Is this node hidden
        bool hidden = false;

        // Cache of absolute transformation
        bool absoluteCached = false;
        Matrix4 absoluteCache;
               
        // Is node attached to node collection?
        //bool isAttached = false;
                
        // Spatial object to intersect, collide and so on
        Spatial spatial = null;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the spatial object to intersect, collide and so on
        /// </summary>
        public Spatial Spatial
        {
            get
            {
                if (spatial == null) spatial = new Spatial(this);
                return spatial;
            }
        }

        /// <summary>
        /// Gets or sets the hierarchy object. It's used to implement 
        /// hierarchical transformations
        /// </summary>
        public Visual Hierarchy
        {
            get { return hierarchy; }
            set 
            {
                if (hierarchy != null) hierarchy.AbsoluteInvalidated -= new EventHandler(OnHierarchyAbsoluteInvalidated);
                hierarchy = value;
                if (hierarchy != null) hierarchy.AbsoluteInvalidated += new EventHandler(OnHierarchyAbsoluteInvalidated);

                absoluteInvalidated.Invoke(this, EventArgs.Empty);
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets local transformation relative 
        /// to the parent node
        /// </summary>
        public ComplexTransformation Transformation
        {
            get { return transformation;  }
            set
            {
                if (transformation != null) transformation.Changed -= new EventHandler(OnTransformationChanged);
                transformation = value;
                if (transformation != null) transformation.Changed += new EventHandler(OnTransformationChanged);

                absoluteInvalidated.Invoke(this, EventArgs.Empty);
                Invalidate();
            }
        }

        /// <summary>
        /// Gets the absolute transformation of this node. 
        /// To calculate the absolute transformation 
        /// we need to traverse parent nodes
        /// </summary>
        public Matrix4 Absolute
        {
            get
            {
                lock (this)
                {
                    if (!absoluteCached)
                    {
                        absoluteCache = (hierarchy != null) ?
                            transformation.Value * hierarchy.Absolute :
                            transformation.Value;
                        absoluteCached = true;
                    }
                }
                return absoluteCache;
            }
        }

        /// <summary>
        /// Gets or sets the rendering technique of the node
        /// </summary>
        public Technique Technique
        {
            get { return technique; }
            set 
            {
                if (technique != null)
                {
                    RemoveFromRenderQueue(this, technique);
                    technique.Invalidated -= new EventHandler(OnTechniqueInvalidated);
                }
 
                technique = value;
                if (technique != null)
                {
                    RegisterToRenderQueue(this, technique);
                    technique.Invalidated += new EventHandler(OnTechniqueInvalidated);
                }
                
                Invalidate();
            }
        }

        /// <summary>
        /// Polygonal geometry to render this node
        /// </summary>
        public Polygonal Polygonal
        {
            get { return polygonal; }
            set 
            {
                if (polygonal != null) polygonal.Changed -= new EventHandler(OnPolygonalChanged);
                polygonal = value;
                if (polygonal != null) polygonal.Changed += new EventHandler(OnPolygonalChanged);

                polygonalInvalidated.Invoke(this, EventArgs.Empty);
                Invalidate();
            }
        }

        /// <summary>
        /// Properties of the node
        /// </summary>
        public PropertyCollection Properties
        {
            get { return properties; }
            set 
            {
                if (properties != null) properties.Invalidated -= new EventHandler(OnPropertiesInvalidated);
                properties = value;
                if (properties != null) properties.Invalidated += new EventHandler(OnPropertiesInvalidated);

                Invalidate();
            }
        }

        /// <summary>
        /// Is this node hidden
        /// </summary>
        public bool IsHidden
        {
            get { return hidden; }
            set 
            {
                if (hidden != value)
                {
                    hidden = value;
                    
                    if (hidden) RemoveFromRenderQueue(this, technique);
                    else RegisterToRenderQueue(this, technique);

                    Invalidate();
                }
            }
        }

        /// <summary>
        /// Is the node attached to a render graph?
        /// </summary>
        internal bool IsAttached
        {
            get { return parents.Count > 0; }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Default constructor
        /// </summary>
        public Visual() : this (null, new DefaultTechnique())
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="polygonal">A polygonal geometry</param>
        /// <param name="technique">A rendering technique</param>
        public Visual(Polygonal polygonal, Technique technique)
        {
            this.polygonal = polygonal;
            this.technique = technique;
            
            if (transformation != null) transformation.Changed += new EventHandler(OnTransformationChanged);
            if (technique != null) technique.Invalidated += new EventHandler(OnTechniqueInvalidated);
            if (polygonal != null) polygonal.Changed += new EventHandler(OnPolygonalChanged);
            if (properties != null) properties.Invalidated += new EventHandler(OnPropertiesInvalidated);
            AbsoluteInvalidated += new EventHandler(OnAbsoluteInvalidated);
        }

        
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="polygonal">A mesh</param>
        public Visual(Polygonal polygonal) : this (polygonal, new DefaultTechnique())
        {
        }

        #endregion

        #region Methods

        

        #endregion

        #region Event's Handlers
        
        void OnTransformationChanged(object sender, EventArgs e)
        {
            absoluteInvalidated.Invoke(this, EventArgs.Empty);
            Invalidate();
        }

        void OnTechniqueInvalidated(object sender, EventArgs e)
        {
            Invalidate();
        }

        void OnPolygonalChanged(object sender, EventArgs e)
        {
            polygonalInvalidated.Invoke(this, EventArgs.Empty);
            Invalidate();
        }

        void OnPropertiesInvalidated(object sender, EventArgs e)
        {            
            Invalidate();
        }

        void OnHierarchyAbsoluteInvalidated(object sender, EventArgs e)
        {
            absoluteInvalidated.Invoke(this, EventArgs.Empty);
            Invalidate();
        }


        void OnAbsoluteInvalidated(object sender, EventArgs e)
        {
            absoluteCached = false;
        }

        #endregion

        #region Internals

        /// <summary>
        /// Attachs node
        /// </summary>
        /// <param name="node">Node</param>
        internal override void Attach(Node node)
        {   
            base.Attach(node);
            if (!hidden) node.RegisterToRenderQueue(this, technique);
            Invalidate();
        }

        /// <summary>
        /// Dettachs node
        /// </summary>
        /// <param name="node">Node</param>
        internal override void Dettach(Node node)
        {   
            if (!hidden)
            {
                // FIXME: a bad way to do it!
                node.RemoveFromRenderQueue(this, technique);
                base.Dettach(node);
                RegisterToRenderQueue(this, technique);
            }
            Invalidate();            
        }

        #endregion

        #region Enumerations

        /// <summary>
        /// Enumerates nodes
        /// </summary>
        /// <returns>A System.Collections.Generic.IEnumerable<Node> that 
        /// can be used to iterate through the collection</returns>
        internal override IEnumerable<Visual> Enumerate()
        {
            yield return this;
        }         

        #endregion
    }
}
